/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package bgu.sonar.util.ui.logs;

import bgu.sonar.util.evt.EventListeners;
import bgu.sonar.util.ex.UncheckedIOException;
import bgu.sonar.util.strings.BoyerMoore;
import bgu.sonar.util.ui.mon.Monitor;
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.util.regex.Pattern;

/**
 *
 * @author bennyl
 */
public class PageDocumentModel implements Closeable {

    private static final int DEFAULT_PAGE_SIZE = 2 * 1024 * 1024; //5m
    private static final int DEFAULT_CHUNK_SIZE = 256 * 1024; //256k
    private int pageSize;
    private File path;
    private RandomAccessFile file;
    private int currentPage;
    private int fileSize;
    private EventListeners<PageDocumentCloseListener> listeners = EventListeners.create(PageDocumentCloseListener.class);
    private String grepStorePath;

    public PageDocumentModel(File path, int pageSize) throws FileNotFoundException {
        this.path = path;
        this.file = new RandomAccessFile(path, "r");
        this.currentPage = 0;
        this.pageSize = pageSize;
        this.fileSize = (int) path.length();
    }

    public void setGrepStorePath(String grepStorePath) {
        this.grepStorePath = grepStorePath;
    }
    
    public EventListeners<PageDocumentCloseListener> getListeners() {
        return listeners;
    }

    public PageDocumentModel(File path) throws FileNotFoundException {
        this(path, DEFAULT_PAGE_SIZE);
    }

    public String getDocumentName() {
        return path.getName();
    }

    public int getNumberOfPages() {
        return (int) Math.ceil((double) fileSize / (double) pageSize);
    }

    /**
     * zero based index
     *
     * @return
     */
    public int getCurrentPageNumber() {
        return currentPage;
    }

    public String getCurrentPageLines() {
        try {
            int pagePos = pageSize * currentPage - (currentPage == 0 ? 0 : 1);
            file.seek(pagePos);
            StringBuilder sb = new StringBuilder();
            final TraceableInputStream trace = new TraceableInputStream();
            InputStreamReader reader = new InputStreamReader(trace);

            String line = readLine(reader); //first line is dropped unless pagePos is 0
            if (pagePos == 0) {
                sb.append(line).append("\n");
            }
            while (trace.getBytesRead() < pageSize && (line = readLine(reader)) != null) {
                sb.append(line).append("\n");
            }

            return sb.toString();
        } catch (IOException ex) {
            throw new UncheckedIOException("problem while reading page: " + currentPage, ex);
        }

    }

    public void gotoPageNumber(int page) {
        if (page > getNumberOfPages() || page < 0) {
            throw new UnsupportedOperationException("page not in bounds");
        }
        currentPage = page;
    }

    @Override
    public void close() throws IOException {
        file.close();
        listeners.fire().onClose(this);
    }

    private static String readLine(InputStreamReader reader) throws IOException {
        StringBuilder sb = new StringBuilder("");
        char[] buf = new char[1];
        int eof = 0;
        while ((eof = reader.read(buf)) > 0 && buf[0] != '\n') {
            sb.append(buf[0]);
        }

        return sb.length() == 0 && eof == -1 ? null : sb.toString();
    }

    /**
     * return any line contains any of the given greps
     * @param grep
     * @param grepInto
     * @param monitor
     * @return
     * @throws Exception 
     */
    public PageDocumentModel grep(String[] grep, File grepInto, Monitor monitor) throws Exception {
//        BoyerMoore patternSearcher = new BoyerMoore(grep);
        StringBuilder patternString = new StringBuilder();
        for (String g : grep){
            patternString.append(Pattern.quote(g)).append("|");
        }
        patternString.delete(patternString.length()-1, patternString.length());
        Pattern p = Pattern.compile(patternString.toString());
        try (PrintWriter pw = new PrintWriter(grepInto)) {
            file.seek(0);
            TraceableInputStream input = new TraceableInputStream();
            InputStreamReader inputReader = new InputStreamReader(input);
            String line;

            while ((line = readLine(inputReader)) != null) {
                if (p.matcher(line).find()) pw.println(line);
                monitor.setProgress((double) input.getBytesRead() / (double) fileSize);
            }
        }
//        LogIndex index = lc.getIndex(path);
//        if (index == null) {
//            monitor.writeln("indexing file for the first time - please wait...");
//            index = lc.createIndex(path, monitor.sub(0.7), true);
//        } else {
//            monitor.incProgress(0.7);
//        }

//        monitor.writeln("searching index please wait...");
//        index.grep(grep, file, grepInto);
//        lc.indexedGrep(grep, file, path, grepInto, monitor);
        monitor.close();
        return new PageDocumentModel(grepInto, pageSize);
    }

    public File getPath() {
        return path;
    }

    String getPathToStoreGrepFiles() {
        //Workspace.getOpenedWorkspace().getPathToInternalData().getAbsolutePath()
        return grepStorePath;
    }

    private class TraceableInputStream extends InputStream {

        private byte[] chunk = new byte[DEFAULT_CHUNK_SIZE];
        private int pos = -1;
        private int len = 0;
        private int bytesRead = 0;

        @Override
        public int read() throws IOException {
            if (len >= 0 && (pos == -1 || pos >= len)) {
                len = file.read(chunk);
                pos = 0;
            }
            if (len < 0) {
                return -1;
            }

            bytesRead++;
            return chunk[pos++];
        }

        public int getBytesRead() {
            return bytesRead;
        }
    }

    public static interface PageDocumentCloseListener {

        void onClose(PageDocumentModel page);
    }
}
